<?php
namespace Socks5;

use Helper\Logger;

require_once "Net/Connection.php";
require_once dirname(__DIR__) . "/Helper/Logger.php";

class Socks5Connection extends \Net\Connection {
    protected $_handshake = false;
    protected $_methodselect = false;
    protected $_forwardTo=null;
    protected $_forwardSize=0;
    
    /**
     * @param bool $b
     * @return $this
     */
    public function setHandShake($b=true) {
        $this->_handshake = $b;
        return $this;
    }
    
    /**
     * @return bool
     */
    public function haveHandshake() {
        return $this->_handshake;
    }
    
    /**
     * @param bool $b
     * @return $this
     */
    public function setMethodSelectd($b=true) {
        $this->_methodselect = $b;
        return $this;
    }
    
    /**
     * @return bool
     */
    public function haveMethodSelect() {
        return $this->_methodselect;
    }
    
    public function buildPackage() {
        $args = func_get_args();
        
        //version
        $package = chr(5);
    
        foreach ($args as $arg) {
            if(is_string($arg)) {
                $package .= $arg;
            } else {
                $package .= chr($arg);
            }
        }
    
        return $package;
    }
    
    public function setForwardTo($socket) {
        if($socket === $this) {
            return $this;
        }
        
        $this->_forwardTo = $socket;
        return $this;
    }
    
    /**
     * @return static|null
     */
    public function getForwardTo() {
        return $this->_forwardTo;
    }
    
    public function sendServerRepRequest($type, $bindIp=null, $bindPort=null) {
        $msg = $this->buildRepPackage($type, $bindIp, $bindPort);
        $this->debugPackageSend($msg);
        $this->send($msg);
        return $this;
    }
    
    public function sendServerMethodSelect($method) {
        $msg = $this->buildPackage($method);
        $this->send($this->buildPackage($method));
        $this->debugPackageSend($msg);
        return $this;
    }
    
    public function buildRepPackage($type, $bindIp=null, $bindPort=null) {
        $bindIp = strval($bindIp);
        $ipary = array_merge(array_map('intval', explode('.', $bindIp)), [0,0,0,0]);
        $bindPort = intval($bindPort);
        $port1 = ($bindPort >> 8) & 0xF;
        $port2 = $bindPort & 0xF;
        
        $addressType = 0;
        
        if($bindIp) {
            $addressType = 1;
        }
        
        return $this->buildPackage($type, 0, $addressType, $ipary[0], $ipary[1], $ipary[2], $ipary[3], $port1, $port2);
    }
    
    public function forward($msg) {
        if($this->getForwardTo()) {
            $len = strlen($msg);
            $log = "{$this} ===($len)===> {$this->getForwardTo()}";
            Logger::record("<--> $log", false);
            
            $option = getopt('', ['rfc']);
            
            if(isset($option['rfc'])) {
                Logger::recordForwardContent($log, $msg);
            }
            
            $this->_forwardSize += $len;
            $this->getForwardTo()->send($msg);
        }
        
        return $this;
    }
    
    public function getForwardSize() {
        return $this->_forwardSize;
    }
    
    public function getForwardSizeHumanReadable() {
        $size = $this->getForwardSize();
        $gb = round($size / (1024*1024*1024), 2);
        $mb = round($size / (1024*1024), 2);
        $kb = round($size / 1024, 2);
        $b = $size;
        
        return array_values(array_filter([
            $gb>1?"{$gb}GB":'',
            $mb>1?"{$mb}MB":'',
            $kb>1?"{$kb}KB":'',
            "{$b}B",
        ]))[0];
    }
    
    public function debugPackageSend($msg) {
        Logger::record('send >>> ' . $this . ' ' . implode(', ', $this->splitPackage($msg)), false);
    }
    
    public function debugPackageRcv($msg) {
        Logger::record('recv <<< ' . $this . ' ' . implode(', ', $this->splitPackage($msg)), false);
    }
    
    public function splitPackage($msg) {
        return array_map('ord', str_split($msg, 1));
    }
}